// 分配内核存储空间的头文件
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/fs.h>

// cdev.h 中已经包含了
// 用于自动把设备挂载到 /dev/ 下面
#include <linux/device.h>

// 分配内核内存空间的头文件
#include <linux/cdev.h>
#include "read_write_data.h"

/**
 * 声明了 
 * copy_to_user()      // 主要用于 read 函数 
 * copy_from_user()    // 主要用于 write 函数 
 */
#include <linux/uaccess.h>

#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 2


int hello_major = HELLO_MAJOR;
int hello_minor = 0;
dev_t hello_dev_t;
int hello_nr_devs = HELLO_NR_DEVS;

module_param(hello_major,int,S_IRUGO);
module_param(hello_minor,int,S_IRUGO);
module_param(hello_nr_devs,int,S_IRUGO);

/**
 * 实际的字符设备结构体，类似于面向对象的继承
 */
struct My_character_dev
{
    struct cdev cdev;
    // 自定义的属性和变量
    // 定义 char 指针使得可以保存任意字符串 
    char * c;

    // 指定字符串的长度
    int length;
};

// 定义结构体指针
struct My_character_dev * my_char_dev;


// 额外创建一个指针
struct class * my_char_class;

// 定义字符设备的操作函数

int my_char_open(struct inode * inode,struct file * fp){

    printk(KERN_INFO "open my_character_dev %d %d \n",iminor(inode),MINOR(inode->i_cdev->dev));
     

    // 1、获取设备结构体的地址 
    // inode->i_cdev 指向的就是 My_character_dev.cdev 

    // 把 My_character_dev.cdev 指针转换为 其外部结构体指针
    // 第一个参数：cdev 指针
    // 第二个参数：外部容器结构体是什么类型
    // 第三个参数：cdev 在外部容器结构体内的名字
    struct My_character_dev * my_dev =  container_of(inode->i_cdev,struct My_character_dev,cdev);
    
    // 2、将设备结构体地址放到文件描述符结构的私有数据中
    //    read() write() 函数都需要这个结构体 来获得字符设备的地址
    fp->private_data = my_dev;

    return 0;
}

/**
 * @param buf 用户空间的缓冲区，也就是要把字符设备的内容保存到的地方
 * @param count 用户要拷贝数据的个数
 * @param f_ops 文件描述的偏移量
 */
ssize_t my_char_read(struct file *fp,char __user *buf ,size_t count,loff_t *f_ops){
    printk(KERN_INFO "read my_character_dev\n");
    // 将字符设备中的字符串给到用户空间中
    //  获得在 open() 函数中 放入的 设备结构体地址
    struct My_character_dev * my_dev = fp->private_data;

    // 本次共计读取了多少个数据
    ssize_t retval =0; 

    if(*f_ops >= my_dev->length){
        // 已经读取到尽头
        return 0;
    }

    if(*f_ops + count > my_dev->length){
        // 用户想读取的内容比 字符设备自身持有的数据还多
        // 减少用户想读取的内容数量
        retval = my_dev->length - *f_ops;
    }

    if(copy_to_user(buf,my_dev->c,retval)){
        return -EFAULT;
    }
    // 更新读取的偏移量
    *f_ops+=retval;

    return retval;
}

ssize_t my_char_write(struct file *fp,const char __user *buf,size_t count,loff_t *f_ops){

    printk(KERN_INFO "write my_character_dev %lu count\n",count);
    
    struct My_character_dev * my_dev = fp->private_data;

    if(my_dev->c ){
        kfree(my_dev->c);
    }

    my_dev->c = NULL;
    my_dev->length=0;

    char * kernel_data;

    if( (kernel_data = kzalloc(count,GFP_KERNEL))<0){
        
        printk(KERN_WARNING "can't allocate kernel memory to write %d\n",*kernel_data);
        return -ENOMEM;
    }


    unsigned long processed=0;
    if((processed = copy_from_user(kernel_data,buf,count))<0){
        return -EFAULT;
    }

    my_dev->c=kernel_data;

    my_dev->length = count;

    //*f_ops = processed;
    
    return count; // 不能返回0，否则会不停地往里面写
}

int my_char_release(struct inode *inode,struct file *fp){
    printk(KERN_INFO "release my_character_dev\n");
    return 0;
}


//把函数注册到结构体中
struct file_operations my_char_file_ops = {
    .owner = THIS_MODULE,
    .read = my_char_read,
    .write= my_char_write,
    .open = my_char_open,
    .release = my_char_release
};

dev_t make_dev(size_t i){
    return MKDEV(hello_major,hello_minor+i);
}


int __init my_char_dev_init(void){
    int result;
    printk(KERN_INFO "begin to register my_character_dev\n");
    if(hello_major){
        hello_dev_t = MKDEV(hello_major,hello_minor);
        // 使用指定的主设备号进行分配
        result = register_chrdev_region(hello_dev_t,hello_nr_devs,"my_character_dev");
    }else{
        result = alloc_chrdev_region(&hello_dev_t,hello_minor,hello_nr_devs,"my_character_dev");
        hello_major = MAJOR(hello_dev_t);

    }

    if(result<0){
        printk(KERN_WARNING "fail to allocate my_character_device\n");
        return result;
    }

    // 给字符设备分配空间
    // GFP_KERNEL  代表在内核中分配空间
    my_char_dev = kzalloc(sizeof(struct My_character_dev)*hello_nr_devs,GFP_KERNEL);

    if (!my_char_dev)
    {
        // 避免内存泄漏，必须执行
        
        
        printk(KERN_WARNING "fail to allocate kernel memory to my_character_dev !\n");
        goto fail;
    }

    for (size_t i = 0; i < hello_nr_devs; i++)
    {
        // 使用指定的文件操作函数结构体来初始化字符设备
        cdev_init(&my_char_dev[i].cdev,&my_char_file_ops);
        my_char_dev[i].cdev.owner = THIS_MODULE;

        dev_t dev = make_dev(i);

        // 将该字符设备添加到系统中
        result = cdev_add(&my_char_dev[i].cdev,dev,1);

        if(result){
            printk(KERN_WARNING "fail to initialize my_character_dev %d\n",result);
            goto failure_kzalloc;
        }
    }

    // 自动挂载到 /dev/下面
    // 第二个参数是 挂载在 /sys/class 下的名字
    my_char_class = class_create("my_char_dev");

    if(!my_char_class){
        printk(KERN_WARNING "fail to create class! \n");
        goto failure_kzalloc;
        
    }

    for (size_t i = 0; i < hello_nr_devs; i++)
    {   
        dev_t dev =make_dev(i);
        // 真正创建 /dev/my_chara0 设备
        device_create(my_char_class,NULL,dev,NULL,"my_chara%lu",i);
    }
    

    
    printk(KERN_INFO "register my_character_dev successfully!\n");

    return 0;

//failure_class:

failure_kzalloc:
    kfree(my_char_dev);
fail: 
    unregister_chrdev_region(hello_dev_t,hello_nr_devs);
    return 0;
}



void __exit my_char_dev_exit(void){

    // 移除设备
    for (size_t i = 0; i < hello_nr_devs; i++)
    {
        dev_t dev = make_dev(i);

        device_destroy(my_char_class,dev);
    }
    
    class_destroy(my_char_class);

    for (size_t i = 0; i < hello_nr_devs; i++)
    {
        cdev_del(&my_char_dev[i].cdev);
    }

    kfree(my_char_dev);

    // 释放申请得到的字符设备号
    unregister_chrdev_region(hello_dev_t,hello_nr_devs);
    printk(KERN_INFO "unregister my_character_dev successfully!\n");

}

module_init(my_char_dev_init);
module_exit(my_char_dev_exit);


//  许可证描述
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("luojunhua");
MODULE_VERSION("V1.0");